﻿using System;
using System.ComponentModel.DataAnnotations;
using System.Data;
using System.Data.Entity.ModelConfiguration;
using System.Globalization;
using System.Reflection;
using System.ComponentModel;
using Supermodel.Exceptions;
using Supermodel.MVC.FormModels.Metadata;
using Supermodel.Reflection;

namespace Supermodel.DomainModel
{
    public class EFCFEnum
    {
        #region Constructors
        public EFCFEnum() : this(0) { }
        private EFCFEnum(int value)
        {
            IntValue = value;
        }
        #endregion

        #region Implicit assignments
        public static implicit operator EFCFEnum(Enum value)
        {
            EFCFEnum me = new EFCFEnum();
            ReflectionHelpers.ExecuteStaticGenericMethod(typeof(EnumExtensions), "SetEnumValue", new[] { value.GetType() }, me, value);
            return me;
        }
        public static implicit operator Enum(EFCFEnum obj)
        {
            return (Enum)ReflectionHelpers.ExecuteStaticGenericMethod(typeof(EnumExtensions), "GetEnumValue", new[] { obj.EnumType }, obj);
        }   
        #endregion      
        
        #region Methods and Properties
        public int IntValue
        {
            get { return _intValue; }
            set
            {
                _intValue = value;
                ValidateValueInEnumTypeRange();
            }
        }

        [NotMapped]
        public Type EnumType
        {
            get { return _enumType; }
            set
            {
                if (_enumType != null && _enumType != value) throw new SupermodelException("EFCFEnum: Cannot change the type of enum stored.");
                _enumType = value;
                if (EnumType != null) Enum.ToObject(EnumType, IntValue);
            }
        }
        //This is only for use with db storage
        public string EnumTypeName
        {
            get
            {
                if (EnumType == null) throw new NoNullAllowedException("EnumType is null");
                return EnumType.FullName + ", " + EnumType.Assembly.FullName;
            }
            set
            {
                EnumType = Type.GetType(value);
                if (EnumType == null) throw new SupermodelException(string.Format("Unable to parse datatype {0}", value));
            }
        }

        public void ValidateValueInEnumTypeRange()
        {
            //Try to conver the int into the enum, it will throw an exception if it cant
            if (EnumType != null) Enum.ToObject(EnumType, IntValue);
        } 
        #endregion

        #region Private Vars
        private int _intValue;
        private Type _enumType = null; 
        #endregion
    }

    public class EFCFEnumTypeMapping : ComplexTypeConfiguration<EFCFEnum>
    {
        public EFCFEnumTypeMapping()
        {
            Property(o => o.IntValue);
            Property(o => o.EnumTypeName);
        }
    }
    
    public static class EnumExtensions
    {
        public static EnumT GetEnumValue<EnumT>(this EFCFEnum me) where EnumT : struct, IConvertible
        {
            if (me.EnumType == null) me.EnumType = typeof(EnumT);
            if (typeof(EnumT) != me.EnumType) throw new SupermodelException("Attempting to read invalid EFCFEnum type");
            return (EnumT)Enum.ToObject(me.EnumType, me.IntValue);
        }
        
        public static EFCFEnum SetEnumValue<EnumT>(this EFCFEnum me, EnumT value) where EnumT : struct, IConvertible
        {
            me.EnumType = typeof(EnumT);
            me.IntValue = value.ToInt32(NumberFormatInfo.InvariantInfo);
            me.ValidateValueInEnumTypeRange();
            return me;
        }        
        
        public static string GetDescription(this object enumerationValue)
        {
            Type type = enumerationValue.GetType();
            if (!type.IsEnum) throw new ArgumentException("EnumerationValue must be of Enum type", "enumerationValue");

            //Tries to find a DescriptionAttribute for a potential friendly name for the enum
            MemberInfo[] memberInfo = type.GetMember(enumerationValue.ToString());
            if (memberInfo.Length > 0)
            {
                object[] attrs = memberInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
                if (attrs.Length > 0) return ((DescriptionAttribute)attrs[0]).Description; //Pull out the description value
            }
            //If we have no description attribute, just return the ToString of the enum
            return enumerationValue.ToString();
        }

        public static int GetScreenOrder(this object enumerationValue)
        {
            Type type = enumerationValue.GetType();
            if (!type.IsEnum) throw new ArgumentException("EnumerationValue must be of Enum type", "enumerationValue");

            //Tries to find a DescriptionAttribute for a potential friendly name for the enum
            MemberInfo[] memberInfo = type.GetMember(enumerationValue.ToString());
            if (memberInfo.Length > 0)
            {
                object[] attrs = memberInfo[0].GetCustomAttributes(typeof(ScreenOrderAttribute), false);
                if (attrs.Length > 0) return ((ScreenOrderAttribute)attrs[0]).Order;
            }
            return 100;
        }
    }
}
